package flare.animate
{
  import flare.util.Maths;
  import flare.util.Vectors;

  /**
   * Transition that runs sub-transitions in sequential order while also
   * invoking a function before each sub-transition is run. Each function
   * must take a Transition instance (often a Transitioner) as input.
   * Function sequences can only be played forwards; any attempt to play
   * one in reverse will result in an error.
   * 
   * <p>Function sequences are useful for ensuring a particular function
   * is run before a sub-transition begins. For example, the function may
   * populate the values of a Transitioner on the fly, or may be used to
   * control other variables or side-effects that affect the subsequent
   * sub-transition(s).</p>
   */
  public class FunctionSequence extends Sequence
  {
    /** @private */
    protected var _funcs:Vector.<Object> = new Vector.<Object>();
    /** @private */
    protected var _offsetStart:Boolean = true;

    /** Flag indicating if extra time should be added to the transition to
     *  offset the running time of invoked functions. True by default. */
    public function get offsetStartTime():Boolean
    {
      return _offsetStart;
    }

    public function set offsetStartTime(b:Boolean):void
    {
      _offsetStart = b;
    }

    // --------------------------------------------------------------------

    /**
     * Creates a new FunctionSequence transition.
     */
    public function FunctionSequence()
    {
      super();
    }

    /**
     * Adds a function call and corresponding transition to this sequence.
     * As the sequence plays, functions will be called at the beginning of
     * their subsequence, with the provided transition passed as the
     * function's sole argument.
     * @param f a function to call at the beginning of a sub-sequence
     * @param t the transition to run after the function call. This
     *  transition will be passed in as an input to the function. This
     *  value should be either a <code>Transition</code> instance or
     *  a number indicating the duration for a new
     *  <code>Transitioner</code>.
     */
    public function push(f:Function, t:*):void
    {
      var tr:Transition = t is Transition ? Transition(t) : Transitioner.instance(t);
      super.add(tr);
      _funcs.push(f);
    }

    /**
     * Removes a sub-transition function from this sequence. The
     * corresponding transition instance will also be removed.
     * @param t the transition to remove
     * @return true if the transition was found and removed, false
     *  otherwise
     */
    public function removeFunction(f:Function):Boolean
    {
      if(running)throw new Error("Transition is running!");
      var idx:int = Vectors.remove(_funcs, f);
      var rem:Boolean = idx > 0;

      if(rem)
      {
        _trans.splice(idx, 1);
        _dirty = true;
      }
      return rem;
    }

    /** @inheritDoc */
    public override function add(t:Transition):void
    {
      super.add(t);
      _funcs.push(null);
    }

    /**
     * Removes a sub-transition from this sequence. Any corresponding
     * function will also be removed.
     * @param t the transition to remove
     * @return true if the transition was found and removed, false
     *  otherwise
     */
    public override function remove(t:Transition):Boolean
    {
      if(running)throw new Error("Transition is running!");
      var idx:int = Vectors.remove(_trans, t);
      var rem:Boolean = idx > 0;

      if(rem)
      {
        _funcs.splice(idx, 1);
        _dirty = true;
      }
      return rem;
    }

    /** @inheritDoc */
    public override function dispose():void
    {
      super.dispose();
      _funcs.length = 0;
    }

    /**
     * Plays this function sequence. Function sequences can not be played
     * in reverse. 
     * @param reverse If true, an error will be thrown and the sequence
     *  will not play.
     */
    public override function play(reverse:Boolean = false):void
    {
      if(reverse)throw new Error("Function sequences can't be played in reverse.");
      super.play(false);
    }

    private function invoke(idx:int, t:Transition):void
    {
      var f:Function = _funcs[idx] as Function;

      if(f != null)
      {
        if(_offsetStart)
        {
          var d:Number = new Date().time;
          f(t);
          d = new Date().time - d;
          _start += d;
        }

        else
        {
          f(t);
        }
      }
    }

    // --------------------------------------------------------------------

    /**
     * Sets up each sub-transition.
     */
    protected override function setup():void
    {
    }

    /**
     * Starts this sequence transition, starting the first sub-transition
     * to be played.
     */
    protected override function start():void
    {
      if(_trans.length > 0)
      {
        var t:Transition = _trans[_idx] as Transition;
        invoke(_idx, t);
        t.doSetup();
        t.doStart(false);
      }
    }

    /**
     * Steps this sequence transition, ensuring that any sub-transitions
     * between the previous and current progress fraction are properly
     * invoked.
     * @param ef the current progress fraction.
     */
    internal override function step(ef:Number):void
    {
      // find the right sub-transition
      var t:Transition, f0:Number, f1:Number, i:int, inc:int;
      f0 = _fracs[_idx] as Number;
      f1 = _fracs[_idx + 1] as Number;
      inc = (ef <= f0 ? -1 : 1);

      for(i = _idx; i >= 0 && i < _trans.length; i += inc)
      {
        // get transition and progress fractions
        t = _trans[i] as Transition;
        f0 = _fracs[i] as Number;
        f1 = _fracs[i + 1] as Number;

        // hand-off to new transition
        if(i != _idx)
        {
          invoke(i, t);
          t.doSetup();
          t.doStart(false);
        }

        if((inc < 0 && ef >= f0) || (inc > 0 && ef <= f1))break;
        t.doStep(inc < 0 ? 0 : 1);
        t.doEnd();
      }
      _idx = i; // set the transition index

      if(_idx >= 0 && _idx < _trans.length)
      {
        // run transition with mapped fraction
        t.doStep(Maths.invLinearInterp(ef, f0, f1));
      }
    }
  } // end of class FunctionSequence
}